#ifndef LAUNCHSITE_H
#define LAUNCHSITE_H

#include <GL/glut.h>
#include <math.h>
#include <list>

#ifndef M_PI
// as much of PI as I could remember...
#define M_PI 3.1415926536f
#endif

#define maxaor 70.0*M_PI/180.0f

//catch the object
class floatbuffer {

public:
	int w, h;

	float *buf;

	floatbuffer() {
		buf = NULL;
		w = -1;
		h = -1;
	}

	~floatbuffer() {
		if (buf)
			delete[] buf;
	}

	void create(int width, int height) {
		if (width > 0 && height > 0) {
			if (buf)
				delete[] buf;

			w = width;
			h = height;
			buf = new float[w * h];
		}
		for (int i = 0; i < w * h; i++) {
			buf[i] = -0.99;

		}
	}
	float getBuf(int x, int z) {
		return buf[z * w + x];
	}

	void setBuf(int x, int z, float y) {
		buf[z * w + x] = y;
	}
};

//the elements where snow gets accumulated
class launchSite {

public:
	GLfloat base, // the height of the object
			accum, // the height of the accumulated snow
			worldx, worldz;

	bool hassnow; // has some snow on it.


	launchSite* neighbor[8];

	launchSite() :
		base(0.0), accum(0.0), worldx(0.0), worldz(0.0) {

	}

	~launchSite() {
	}

	inline void plotbase(bool texture = false) {
		//cout<<worldx<<" "<<base<<" "<<worldy<<endl;
		if (texture) {
			glPushMatrix();
			{
				glTranslatef(worldx, base, worldz);
				//
				glutSolidSphere(0.02, 6, 6);
			}
			glPopMatrix();
		} else {
			glBegin(GL_POINTS);
			glVertex3f(worldx, base, worldz);
			glEnd();
		}
	}

	inline void plotaccum(bool texture = false) {
		//	cout<<worldx<<" "<<accum<<" "<<worldy<<endl;
		if (texture) {
			glPushMatrix();
			{
				glTranslatef(worldx, accum, worldz);

				glutSolidSphere(0.02, 6, 6);
			}
			glPopMatrix();
		} else {
			glBegin(GL_POINTS);
			glVertex3f(worldx, accum, worldz);
			glEnd();
		}
	}

	void accumulate(float amount) {
		launchSite* n;

		accum += amount;

		if (!hassnow)
			hassnow = true;
		else
			for (int i = 0; i < 8; i++) {

				n = neighbor[i];
				if (n && accum > n->accum) {
					float w, h, d, aor, avalanche;

					w = sqrt((worldx - n->worldx) * (worldx - n->worldx)
							+ (worldz - n->worldz) * (worldz - n->worldz));

					h = accum - n->accum;
					aor = atan(h / w);

					if (aor > maxaor) {
						d = w / tan(maxaor);
						avalanche = (accum - n->accum - d) / 2.0f;

						// make sure that we're not avalanching more snow than we have
						if (avalanche > accum - base) {
							avalanche = accum - base;
							accum = base;
							hassnow = false;
						} else
							accum -= avalanche;
						n->accumulate(avalanche);
					}
				}
			}

	}

};

//the set of lauchSite
class SiteSet {

public:

	int w, h;

	launchSite **elements;

	SiteSet(floatbuffer* zbuf, bool zero = false) :
		elements(0), w(0), h(0) {

		launchSite* e;

		int x, z;

		w = zbuf->w;
		h = zbuf->h;

		elements = new launchSite*[w];
		for (x = 0; x < w; x++) {
			elements[x] = new launchSite[h];

			for (z = 0; z < h; z++) {
				e = &(elements[x][z]);
				if (zero) {
					e->base = -1;
				} else {
					e->base = zbuf->buf[z * w + x];
				}
				e->accum = e->base;
				e->hassnow = false;
				e->worldx = (float) x / w * 2.0 - 1.0;
				e->worldz = (float) z / h * 2.0 - 1.0;
			}
		}

		for (x = 0; x < w; x++) {
			for (z = 0; z < h; z++) {
				e = &(elements[x][z]);
				e->neighbor[0] = getneighbour(x - 1, z - 1);
				e->neighbor[1] = getneighbour(x, z - 1);
				e->neighbor[2] = getneighbour(x + 1, z - 1);
				e->neighbor[3] = getneighbour(x - 1, z);
				e->neighbor[4] = getneighbour(x + 1, z);
				e->neighbor[5] = getneighbour(x - 1, z + 1);
				e->neighbor[6] = getneighbour(x, z + 1);
				e->neighbor[7] = getneighbour(x + 1, z + 1);
			}
		}

		launchSite* n;
		e = &(elements[1][1]);

	}

	~SiteSet() {
		if (elements)
			delete elements;
	}

	launchSite* getneighbour(int x, int y) {
		if (x >= 0 && y >= 0 && x < w && y < h)
			return &elements[x][y];
		else
			return NULL;
	}

	void RecalculateAvalanches() {
		for (int y = 0; y < h; y++) {
			for (int x = 0; x < w; x++) {
				elements[x][y].accumulate(0.0f);
			}
		}
	}

};

#endif
